// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include <stacktrace.h>
#include <errno.h>

#include "runtime_types.h"

#include "util.h"
#include "print.h"
#include "proc.h"
#include "backtrace.h"


static int
stacktrace_read_uintptr(void *arg, uintptr_t addr, uintptr_t *data)
{
	(void) arg;
	*data = *((uintptr_t *) addr);
	return EOK;
}

int32
runtime_callers (int32 skip, uintptr *pcbuf, int32 m)
{
	// FIXME: Make this work correctly.

	stacktrace_prepare();

	stacktrace_t st;
	uintptr_t fp = stacktrace_fp_get();
	uintptr_t pc = stacktrace_pc_get();

	st.op_arg = NULL;
	st.read_uintptr = stacktrace_read_uintptr;

	int32 index = 0;

	while (index < m && stacktrace_fp_valid(&st, fp)) {
		if (skip > 0) {
			skip--;
		} else {
			pcbuf[index] = pc;
			index++;
		}

		(void) stacktrace_ra_get(&st, fp, &pc);
		(void) stacktrace_fp_prev(&st, fp, &fp);
	}

	return index;
}

int
Callers(int skip, struct __go_open_array pc)
{
	return runtime_callers(skip, (uintptr *)pc.__values, pc.__count);
}

bool
runtime_showframe(const char *s)
{
	static int32 traceback = -1;
	
	if(traceback < 0)
		traceback = runtime_gotraceback();
	return traceback > 1 || (__builtin_strchr(s, '.') != nil && __builtin_memcmp(s, "runtime.", 7) != 0);
}

// Print a stack trace for the current goroutine.
void runtime_traceback()
{
	uintptr pcbuf[100];
	int32 c;

	c = runtime_callers(1, pcbuf, sizeof pcbuf / sizeof pcbuf[0]);
	runtime_printtrace(pcbuf, c);
}

void runtime_printtrace(uintptr * pcbuf, int32 c)
{
	int32 i;

	for (i = 0; i < c; ++i) {
		struct __go_string fn;
		struct __go_string file;
		int line;

		if (runtime_getpcinfo(pcbuf[i], &fn, &file, &line) &&
				runtime_showframe((const char *)fn.__data)) {
			runtime_printf("%S\n", fn);
			runtime_printf("\t%S:%d\n", file, line);
		} else {
			runtime_printf("%d: %x; ", i, pcbuf[i]);
			runtime_flush();
		}
	}

	runtime_printf("\n-- end of stack trace --\n");
}

void runtime_tracebackothers(G* g)
{
	// TODO
}

void
runtime_goroutineheader(G *g)
{
	const char *status;

	switch(g->status) {
	case Grunning:
		status = "running";
		break;
	case Gwaiting:
		status = "waiting";
		break;
	default:
		status = "???";
		break;
	}
	runtime_printf("goroutine %d [%s]:\n", g->goid, status);
}

void
runtime_goroutinetrailer(G *g)
{
	if(g != nil && g->gopc != 0 && g->goid != 1) {
		struct __go_string fn;
		struct __go_string file;
		int line;

		if(runtime_getpcinfo(g->gopc - 1, &fn, &file, &line)) {
			runtime_printf("created by %s\n", fn.__data);
			runtime_printf("\t%s:%d\n", file.__data, line);
		} else {
			runtime_printf("created at %x\n", g->gopc);
		}
	}
}

struct Traceback
{
	G* gp;
	uintptr pcbuf[100];
	int32 c;
};

int32
runtime_gotraceback(void)
{
/*
	const byte *p;

	p = runtime_getenv("GOTRACEBACK");
	if(p == nil || p[0] == '\0')
		return 1;	// default is on
	return runtime_atoi(p);
*/
	return 1;
}

// The function that returns function/file/line information.
static _Bool (*infofn)(uintptr pc, struct __go_string *function, struct __go_string *file, int *line);
// The function that returns the value of a symbol, used to get the entry address of a function.
static _Bool (*symvalfn)(struct __go_string, uintptr *val);

_Bool runtime_getpcinfo(uintptr pc, struct __go_string *function, struct __go_string *file, int *line)
{
	return infofn != NULL && infofn(pc, function, file, line);
}

_Bool runtime_getsymbolvalue(struct __go_string sym, uintptr *val)
{
	return symvalfn != NULL && symvalfn(sym, val);
}

extern void RegisterDebugLookup(_Bool (*ifn)(uintptr, struct __go_string *, struct __go_string *, int *),
	_Bool (*sfn)(struct __go_string, uintptr *))
	__asm__("runtime.RegisterDebugLookup");

void RegisterDebugLookup(_Bool (*ifn)(uintptr, struct __go_string *, struct __go_string *, int *),
	_Bool (*sfn)(struct __go_string, uintptr *)) {

	infofn = ifn;
	symvalfn = sfn;
}
